﻿using Mapster;
using System.Linq;
using System.Linq.Dynamic.Core;
using YWM.Application.Linq.Extensions;
using YWM.Common.Extensions;
using YWM.ContextPools;
using YWM.Dto;
using YWM.Entities;
using YWM.Repositories;

namespace YWM.Application
{
    /// <summary>
    /// This is a common base class for CrudService and AsyncCrudService classes.
    /// Inherit either from CrudService or AsyncCrudService, not from this class.
    /// </summary>
    public abstract class CrudServiceBase<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput> : AppService
        where TEntity : class, IEntityOfTKey<TKey>
        where TEntityDto : IEntityDto<TKey>
        where TUpdateInput : IEntityDto<TKey>
    {
        protected IDbContextPool DbContextPool { get; private set; }

        protected IRepository<TEntity, TKey> Repository { get; private set; }

        protected CrudServiceBase()
        {
            DbContextPool = YApp.Resolve<IDbContextPool>();
            Repository = YApp.Resolve<IRepository<TEntity, TKey>>();
        }

        /// <summary>
        /// Should apply sorting if needed.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <param name="input">The input.</param>
        protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetAllInput input)
        {
            //Try to sort query if available
            ISortedResultRequest sortInput = input as ISortedResultRequest;
            if (sortInput != null)
            {
                if (!sortInput.Sorting.IsNullOrWhiteSpace())
                {
                    return query.OrderBy(sortInput.Sorting);
                }
            }

            //IQueryable.Task requires sorting, so we should sort if Take will be used.
            if (input is ILimitedResultRequest)
            {
                return query.OrderByDescending(e => e.Id);
            }

            //No sorting
            return query;
        }

        /// <summary>
        /// Should apply paging if needed.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <param name="input">The input.</param>
        protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetAllInput input)
        {
            //Try to use paging if available
            IPagedResultRequest pagedInput = input as IPagedResultRequest;
            if (pagedInput != null)
            {
                return query.PageBy(pagedInput);
            }

            //Try to limit query result if available
            ILimitedResultRequest limitedInput = input as ILimitedResultRequest;
            if (limitedInput != null)
            {
                return query.Take(limitedInput.MaxResultCount);
            }

            //No paging
            return query;
        }

        /// <summary>
        /// This method should create <see cref="IQueryable{TEntity}"/> based on given input.
        /// It should filter query if needed, but should not do sorting or paging.
        /// Sorting should be done in <see cref="ApplySorting"/> and paging should be done in <see cref="ApplyPaging"/>
        /// methods.
        /// </summary>
        /// <param name="input">The input.</param>
        protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetAllInput input)
        {
            return Repository.GetAll();
        }

        /// <summary>
        /// Maps <see cref="TEntity"/> to <see cref="TEntityDto"/>.
        /// It uses <see cref="IObjectMapper"/> by default.
        /// It can be overrided for custom mapping.
        /// </summary>
        protected virtual TEntityDto MapToEntityDto(TEntity entity)
        {
            return entity.Adapt<TEntityDto>();
        }

        /// <summary>
        /// Maps <see cref="TEntityDto"/> to <see cref="TEntity"/> to create a new entity.
        /// It uses <see cref="IObjectMapper"/> by default.
        /// It can be overrided for custom mapping.
        /// </summary>
        protected virtual TEntity MapToEntity(TCreateInput createInput)
        {
            return createInput.Adapt<TEntity>();
        }
    }
}
